/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "command_apdu.h"

#include <securec.h>
#include <stddef.h>

#include "apdu_core_defines.h"
#include "command_apdu_inner.h"
#include "logger.h"

CommandApdu *CreateCommandApdu(const ApduCommandHeader *header, const uint8_t *data, uint32_t dataLength,
    uint32_t expectedLength)
{
    if (header == NULL) {
        LOG_ERROR("invalid header");
        return NULL;
    }

    // cmd apdu case 1 defines in ISO/IEC 7816-3
    if (dataLength == 0 && expectedLength == 0) {
        return CreateCommandApduCaseOne(header);
    }

    // cmd apdu case 2 defines in ISO/IEC 7816-3
    if (dataLength == 0 && expectedLength != 0) {
        return CreateCommandApduCaseTwo(header, expectedLength);
    }

    // cmd apdu case 3 defines in ISO/IEC 7816-3
    if (dataLength != 0 && expectedLength == 0) {
        return CreateCommandApduCaseThree(header, data, dataLength);
    }

    // cmd apdu case 4 defines in ISO/IEC 7816-3
    if (dataLength != 0 && expectedLength != 0) {
        return CreateCommandApduCaseFour(header, data, dataLength, expectedLength);
    }

    LOG_ERROR("unreachable code in CreateCommandApdu");
    return NULL;
}

const uint8_t *GetApduCommandBuffer(const CommandApdu *commandApdu, uint32_t *bufferSize)
{
    if (commandApdu == NULL || bufferSize == NULL) {
        return NULL;
    }

    *bufferSize = commandApdu->totalSize;

    return (const uint8_t *)(&commandApdu->header);
}

void DestroyCommandApdu(CommandApdu *commandApdu)
{
    if (commandApdu == NULL) {
        LOG_ERROR("invalid commandApdu");
        return;
    }

    (void)memset_s(commandApdu, sizeof(CommandApdu), 0, sizeof(CommandApdu));
    free(commandApdu);
}

// in ISO/IEC 7816-3 command-response pairs case 1, no command data field, no response data field
CommandApdu *CreateCommandApduCaseOne(const ApduCommandHeader *header)
{
    if (header == NULL) {
        LOG_ERROR("CreateCommandApduCaseOne invalid header");
        return NULL;
    }

    uint32_t size = sizeof(CommandApdu);
    CommandApdu *out = malloc(size);
    if (out == NULL) {
        LOG_ERROR("CreateCommandApduCaseOne malloc error");
        return NULL;
    }
    (void)memset_s(out, size, 0, size);

    out->header.cla = header->cla;
    out->header.ins = header->ins;
    out->header.p1 = header->p1;
    out->header.p2 = header->p2;
    out->totalSize = sizeof(ApduCommandHeader);

    return out;
}

// in ISO/IEC 7816-3 command-response pairs case 2, no command data field, yes response data field
CommandApdu *CreateCommandApduCaseTwo(const ApduCommandHeader *header, uint32_t expectedLength)
{
    if (header == NULL) {
        LOG_ERROR("invalid header");
        return NULL;
    }

    if (expectedLength > MAX_APDU_RESP_SIZE) {
        LOG_ERROR("Case2E is not currently supported now");
        // Case-2E The extended Le field consists of C(5) = '00' and C(6) C(7) encoding Ne from 1 to 65536 ('0000' means
        // the maximum, 65 536). Consequently, n = 7.
        return NULL;
    }

    // apdu case-2S
    uint32_t size = sizeof(CommandApdu) + sizeof(uint8_t);
    CommandApdu *out = malloc(size);
    if (out == NULL) {
        LOG_ERROR("malloc error");
        return NULL;
    }
    (void)memset_s(out, size, 0, size);

    // 0 means the maximum, 256
    uint32_t ne = (expectedLength == MAX_APDU_RESP_SIZE) ? 0 : expectedLength;

    out->header.cla = header->cla;
    out->header.ins = header->ins;
    out->header.p1 = header->p1;
    out->header.p2 = header->p2;
    out->body[0] = (uint8_t)ne;

    out->totalSize = sizeof(ApduCommandHeader) + sizeof(uint8_t);
    return out;
}

// in ISO/IEC 7816-3 command-response pairs case 3, yes command data field, no response data field
CommandApdu *CreateCommandApduCaseThree(const ApduCommandHeader *header, const uint8_t *data, uint32_t dataLength)
{
    if (header == NULL) {
        LOG_ERROR("invalid header");
        return NULL;
    }

    if (data == NULL || dataLength == 0) {
        LOG_ERROR("invalid data");
        return NULL;
    }

    if (dataLength > MAX_APDU_DATA_SIZE) {
        LOG_ERROR("Case-3E is not currently supported now");
        return NULL;
    }

    // apdu case-3S
    uint32_t size = sizeof(CommandApdu) + dataLength + sizeof(uint8_t);
    CommandApdu *out = malloc(size);
    if (out == NULL) {
        LOG_ERROR("malloc error");
        return NULL;
    }
    (void)memset_s(out, size, 0, size);
    out->header.cla = header->cla;
    out->header.ins = header->ins;
    out->header.p1 = header->p1;
    out->header.p2 = header->p2;

    out->body[0] = (uint8_t)dataLength;

    if (memcpy_s(&out->body[1], dataLength, data, dataLength) != EOK) {
        LOG_ERROR("memcpy_s error");
        free(out);
        return NULL;
    }
    out->totalSize = sizeof(ApduCommandHeader) + dataLength + sizeof(uint8_t);
    return out;
}

// in ISO/IEC 7816-3 command-response pairs case 4, yes command data field, yes response data field
CommandApdu *CreateCommandApduCaseFour(const ApduCommandHeader *header, const uint8_t *data, uint32_t dataLength,
    uint32_t expectedLength)
{
    if (header == NULL) {
        LOG_ERROR("invalid header");
        return NULL;
    }

    if (data == NULL || dataLength == 0 || expectedLength == 0) {
        LOG_ERROR("invalid data");
        return NULL;
    }

    if (dataLength > MAX_APDU_DATA_SIZE || expectedLength > MAX_APDU_RESP_SIZE) {
        LOG_ERROR("Case4E is not currently supported now");
        // Case-4E The extended Lc field consists of C(5) = '00' and C(6) C(7) ≠ '0000', encoding Nc from 1 to  65 535.
        // The data field consists of C(8) to C(7+Nc). The extended Le field consists of C(8+Nc) C(9+Nc)
        // encoding Ne from 1 to 65 536 ('0000' means the maximum, 65 536). Consequently, n = 9 + (C(6) C(7)).
        return NULL;
    }

    // apdu case-4S
    uint32_t size = sizeof(CommandApdu) + dataLength + sizeof(uint8_t) + sizeof(uint8_t);
    CommandApdu *out = malloc(size);
    if (out == NULL) {
        LOG_ERROR("malloc error");
        return NULL;
    }
    (void)memset_s(out, size, 0, size);
    out->header.cla = header->cla;
    out->header.ins = header->ins;
    out->header.p1 = header->p1;
    out->header.p2 = header->p2;

    out->body[0] = (uint8_t)dataLength;

    if (memcpy_s(&out->body[1], dataLength, data, dataLength) != EOK) {
        LOG_ERROR("memcpy_s error");
        free(out);
        return NULL;
    }

    // 0 means the maximum, 256
    uint32_t ne = (expectedLength == MAX_APDU_RESP_SIZE) ? 0 : expectedLength;
    out->body[dataLength + 1] = (uint8_t)ne;

    out->totalSize = sizeof(ApduCommandHeader) + dataLength + sizeof(uint8_t) + sizeof(uint8_t);
    return out;
}
